package com.antfin.exam.bonus.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.CannotAcquireLockException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Service;

/**
 * 分布式锁
 */
@Service
public class DistributedLockService {
    protected Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;


    private final int maxRetryCount = 100;      //最大重试次数
    private int defaultExpireSeconds = 5;       //redis默认超时时间(5秒）
    private long sleepMillis = 100;             //如果没有取到锁，进入下一次获取，默认等待时间

    public boolean lock(final String key, final String value) {
        return lock(key, value, defaultExpireSeconds);
    }

    private String getLockKeyData(String key) {
        return String.format("%s.lock", key);
    }

    private String getLockValueData(String value) {
        return String.format("locked:%s", value);
    }

    /**
     * 获得锁
     *
     * @param key
     * @param value
     * @return
     */
    public boolean lock(final String key, final String value, long expireSeconds) {
        boolean result = redisTemplate.execute((RedisConnection connection) -> {
            RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
            byte[] keyByte = serializer.serialize(getLockKeyData(key));
            byte[] valueByte = serializer.serialize(getLockValueData(value));
            boolean isOk = false;
            for (int i = 1; i <= maxRetryCount; i++) {
                isOk = connection.setNX(keyByte, valueByte);
                if (isOk) {
                    //必需要设置超时，有一个service获取完lock之后，挂了，会导致锁永远不释放
                    connection.expire(keyByte, expireSeconds);
                    break;
                } else {
                    try {
                        Thread.sleep(sleepMillis);//休眠
                    } catch (Exception e) {
                        logger.error("线程休眠出错",e);
                    }
                }
            }
            if (!isOk) {
                throw new CannotAcquireLockException("取分布式锁已经做过最大的尝试，还是没有成功");
            }
            return isOk;
        });
        return result;
    }

    /**
     * 释放锁
     *
     * @param key
     * @param value
     */
    public void unlock(final String key, final String value) {
        redisTemplate.execute((RedisConnection connection) -> {
            RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
            byte[] keyByte = serializer.serialize(getLockKeyData(key));
            byte[] bs = connection.get(keyByte);
            if (bs == null) {
                return false;
            }
            String valueData = getLockValueData(value);
            if ((valueData).equals(new String(bs))) {
                return connection.del(keyByte) == 1;
            } else {
                return false;
            }
        });
    }
}
